#include "ObjetoMovible.h"

#include <algorithm>

#define FPS 20

#define LAPSUS_ANIMACIONES 1000/FPS

using namespace std;

const int EstadoBasico::Movimiento = 0;
const int EstadoBasico::Saltando = 1;
const int EstadoBasico::Quieto = 2;
const int EstadoBasico::Animando = 3;

const vector<int> EstadoBasico::acciones = make_vector<int>() << Movimiento << Saltando;
const vector<int> EstadoBasico::animaciones = make_vector<int>() << Quieto << Animando;

ObjetoMovible::ObjetoMovible(int x, int y, int maximoX, int maximoY, Orientacion orientacion) : ObjetoEstatico(x, y, maximoX, maximoY, orientacion) {
	
	Punto pos(x, y);

	this->posicion = pos;
	this->posAnterior = pos;
	this->velocidad = 0;
	this->velocidadX = 0;
	this->velocidadY = 0;
	estado = EstadoBasico::Quieto;

    lastTime = 0;
    lastTimeSalto = 0;
    lastTimeActualizar = 0;
    waiting = false;
    this->animacionActual = 0;
    setRealY(y);
    
    activo = true;
    colisionandoX = false;
    colisionandoY = false;

    estadoDerecha = SinMovimiento;
	estadoIzquierda = SinMovimiento;
	estadoSalto = SinMovimiento;
	
	this->maximoX = maximoX;
	this->maximoY = maximoY;
	srand(time(NULL));
	v_personajes = vector<ObjetoMovible*>();
}

ObjetoMovible::~ObjetoMovible() {
	// TODO Auto-generated destructor stub
}

void ObjetoMovible::setRealY(double y){
	this->realY = y;
}

void ObjetoMovible::setSalto(int salto) {
	this->salto = salto;
}

EstadoMovimiento* ObjetoMovible::getDirEstadoDerecha() {
	return &estadoDerecha;
}

EstadoMovimiento* ObjetoMovible::getDirEstadoIzquierda() {
	return &estadoIzquierda;
}

EstadoMovimiento* ObjetoMovible::getDirEstadoSalto() {
	return &estadoSalto;
}

void ObjetoMovible::setEstado(int estado){

	vector<int> estadosAnimaciones = EstadoBasico::animaciones;
	vector<int> estadosAcciones= EstadoBasico::acciones;
	
	if (find(estadosAnimaciones.begin(), estadosAnimaciones.end(), this->estado) != estadosAnimaciones.end()) {
		int posicion = indexEstado(this->estado);
		if (posicion >= 0) {
			v_animaciones.at(animacionActual)->Reiniciar();
		}
	}
	
	else if (find(estadosAcciones.begin(), estadosAcciones.end(), this->estado) != estadosAcciones.end()) {
		int posicion = indexEstado(this->estado);
		if (posicion >= 0) {
			v_acciones.at(posicion)->Reiniciar();
		}
	}
	
	if (find(estadosAcciones.begin(), estadosAcciones.end(), estado) != estadosAcciones.end()) {
		animacionActual = (indexEstado(estado) >= 0) ? indexEstado(estado) : 0;
	}
	else {
		animacionActual = 0;
	}

	this->estado = estado;
}

int ObjetoMovible::getEstado(){
	return estado;
}

int ObjetoMovible::getPrioridad() {
	return prioridad;
}

void ObjetoMovible::setActivo(bool activo) {
	this->activo = activo;
}

int ObjetoMovible::getActivo() {
	return activo;
}

double ObjetoMovible::getRealY(){
	return realY;
}

Punto ObjetoMovible::getPosicionAnt() {
	return posAnterior;
}

int ObjetoMovible::getSalto(){
	return this->salto;
}

double ObjetoMovible::getVelocidadY(){
	return this->velocidadY;
}

int ObjetoMovible::getVelocidad() {
	return velocidad;
}

int ObjetoMovible::getVelocidadX() {
	return this->velocidadX;
}

void ObjetoMovible::setVelocidadX(int vel) {
	this->velocidadX = vel;
}

void ObjetoMovible::MoverIzquierda(Uint32 deltaTime) {

	setPosicion(getPosicion().getX() - velocidad*deltaTime/1000, getY());
	
	if (estado != EstadoBasico::Movimiento && estado != EstadoBasico::Saltando) {
		setEstado(EstadoBasico::Movimiento);
	}
	if(orientacion != Izquierda){
		setOrientacion(Izquierda);
	}
	setVelocidadX(-1);
}

void ObjetoMovible::MoverDerecha(int maximoX, Uint32 deltaTime) {

	setPosicion(getPosicion().getX() + velocidad*deltaTime/1000, getY());

	if (estado != EstadoBasico::Movimiento && estado != EstadoBasico::Saltando ) {
		setEstado(EstadoBasico::Movimiento);

	}

	if(orientacion != Derecha){
		setOrientacion(Derecha);
	}
	setVelocidadX(1);
}

void ObjetoMovible::Saltar() {
	
	if(estado != EstadoBasico::Saltando){
		this->setVelocidadY(getSalto());
		this->setEstado(EstadoBasico::Saltando);
	}
}

void ObjetoMovible::Frenar(){
	
	if (estado == EstadoBasico::Movimiento)
		setEstado(EstadoBasico::Quieto);
	setVelocidadX(0);
}

void ObjetoMovible::setPosicion(int X, int Y){

	posAnterior=posicion;

	posicion.setX(X);
	posicion.setY(Y);
}

void ObjetoMovible::caer(Uint32 deltaTime, int maximoY) {

	if (deltaTime > 60) deltaTime = 60;
	int a = (int)deltaTime;

	if (estado == EstadoBasico::Saltando) {
		if ((getY() + getAlto()) <= piso) {

			setRealY(getY() - getVelocidadY()*a/1000 + gravedad*a*a/2000000);
			setPosicion(getX(), getRealY());

			if ((getY() + getAlto()) >= piso){
//				setVelocidadY(0);
//				setEstado(EstadoBasico::Quieto);
//				setRealY(piso -getAlto());
//				setPosicion(getX(), getRealY());
				//setPosicion(getX(),0);
				if (activo != false) Morir();
			} else setVelocidadY(getVelocidadY() - gravedad*a/1000);
		}
	}
	else if ((getY() + getAlto()) < piso) {

		setRealY(getY() - getVelocidadY()*a/1000 + gravedad*a*a/2000000);
		if (prioridad==3) if ((int)getRealY() == getY()) realY++;  // LINEA CABEZAAAAAA WARNINGGGG ! ! !
		setPosicion(getX(), (int)getRealY());

		if ((getY() + getAlto()) >= piso){
//			setVelocidadY(0);
//			setEstado(EstadoBasico::Quieto);
//			setRealY(piso -getAlto());
//			setPosicion(getX(), getRealY());
			//setPosicion(getX(),0);
			if (activo != false) Morir();
		} else setVelocidadY(getVelocidadY() - gravedad*a/1000);
	}
	if ((getY() + getAlto()) > piso) {
//		setVelocidadY(0);
//		setEstado(EstadoBasico::Quieto);
//		setRealY(piso - getAlto());
//		setPosicion(getX(), getRealY());
		//setPosicion(getX(),0);
		if (activo != false) Morir();
	}
}

int ObjetoMovible::getPiso() {
	return piso;
}

void ObjetoMovible::setVelocidad(int vel) {
	velocidad = vel;
}

void ObjetoMovible::setVelocidadY(double vel){
	velocidadY=vel;
}

void ObjetoMovible::setPiso(int x) {
	piso = x;
}

void ObjetoMovible::setPeriodoSalto(){
	vector<Animacion*> acciones = getAcciones();
	int cuadros = acciones.at(EstadoBasico::Saltando)->getCantCuadros();

	gravedad = (gravedad) ? gravedad : GRAVEDAD_DEFAULT;
	periodoSalto = (2000*getSalto()/gravedad)/cuadros;
}

void ObjetoMovible::setGravedad(int gravedad) {
	this->gravedad = gravedad;
}

vector<Animacion*> ObjetoMovible::getAcciones() {
	return v_acciones;
}

void ObjetoMovible::addAccion(int idImagen, int cuadros) {
	Animacion *anima= new Animacion (cuadros, 0, idImagen, ancho, alto);
    v_acciones.push_back(anima);
}

void ObjetoMovible::CambiarSentido(int posX, int anchoPlat, bool ownPos) {

	if (estadoDerecha == Moviendo && estadoIzquierda == SinMovimiento) {
		if (!ownPos) setPosicion(posX - getAncho(), getY());
		estadoDerecha = SinMovimiento;
		estadoIzquierda = Empezo;
	}
	
	else if (estadoIzquierda == Moviendo && estadoDerecha == SinMovimiento) {
		if (!ownPos) setPosicion(posX + anchoPlat, getY());
		estadoDerecha = Empezo;
		estadoIzquierda = SinMovimiento;
	}
	else if (estadoDerecha == SinMovimiento && estadoIzquierda == SinMovimiento) {
		int lado = rand() & 2;
		if (lado) estadoDerecha = Empezo;
		else estadoIzquierda = Empezo;
	}
}

void ObjetoMovible::setLastTimeActualizar(Uint32 time) {
	lastTimeActualizar = time;
}

int ObjetoMovible::indexEstado(int estado) {

	return -1;
}

void ObjetoMovible::Actualizar(Uint32 time) {
	
	if (estadoDerecha == SinMovimiento && estadoIzquierda == SinMovimiento && estadoSalto == SinMovimiento)
		return;
		
	if (estadoDerecha == Moviendo) {
		MoverDerecha(maximoX, time - lastTimeActualizar);
	}
	
	if (estadoDerecha == Empezo) {
		estadoDerecha = Moviendo;
		if (estado != EstadoBasico::Saltando)
			setEstado(EstadoBasico::Movimiento);
	}
	
	if (estadoIzquierda == Moviendo) {
		MoverIzquierda(time - lastTimeActualizar);
	}
	
	if (estadoIzquierda == Empezo) {
		estadoIzquierda = Moviendo;
		if (estado != EstadoBasico::Saltando)
			setEstado(EstadoBasico::Movimiento);
	}
	
	if (estadoSalto == Empezo) {
		Saltar();
		estadoSalto = Moviendo;
	}
	
	if ( (estadoDerecha == Freno && estadoIzquierda == SinMovimiento) ||
		 (estadoIzquierda == Freno && estadoDerecha == SinMovimiento) ||
		 (estadoIzquierda == Freno && estadoDerecha == SinMovimiento) ) {
		Frenar();
		estadoDerecha = SinMovimiento;
		estadoIzquierda = SinMovimiento;
	}
}

int ObjetoMovible::getEstadoSalto() {
	return estadoSalto;
}

pair<idImagen_t, int> ObjetoMovible::getDibujable(Uint32 time) {
	
	vector<Animacion*> animaciones;
	vector<int> estadosAnimaciones = EstadoBasico::animaciones;
	vector<int> estadosAcciones = EstadoBasico::acciones;

	bool enAnimacion = find(estadosAnimaciones.begin(), estadosAnimaciones.end(), estado) != estadosAnimaciones.end();
	bool enAccion = find(estadosAcciones.begin(), estadosAcciones.end(), estado) != estadosAcciones.end();

	if (enAnimacion) {
		animaciones = v_animaciones;
		if (indexEstado(estado) < 0) {
			animaciones = v_acciones;
			animacionActual = 0;
		}
	}
	
	else if (enAccion) {
		animaciones = v_acciones;
		animacionActual = (indexEstado(estado) >= 0) ? indexEstado(estado) : 0;
	}

	animaciones.at(animacionActual);
	idImagen_t idImagen = animaciones.at(animacionActual)->getIdImagen();
	int numCuadro = animaciones.at(animacionActual)->getCuadro();

	if (orientacion == Izquierda) {
		idImagen++;
		numCuadro = animaciones.at(animacionActual)->getCantCuadros() - numCuadro - 1;
	}

	if (!waiting && time - lastTime >= LAPSUS_ANIMACIONES) {
		lastTime = time;
		if (estado == EstadoBasico::Saltando) {
			if (time - lastTimeSalto >= periodoSalto) {
				animaciones.at(animacionActual)->aumentarCuadro();
				lastTimeSalto = time;
			}
		}
		else if (!animaciones.at(animacionActual)->aumentarCuadro())
			waiting = true;
	}
	
	if (waiting && time + LAPSUS_ANIMACIONES - lastTime >= animaciones.at(animacionActual)->getPeriodo()) {
		if (enAnimacion) {
			if ( animacionActual == animaciones.size() - 1) 
				animacionActual = 0;
			else
				animacionActual++;
		}
		
		animaciones.at(animacionActual)->Reiniciar();
		waiting = false;
    }

    return pair<idImagen_t, int> (idImagen, numCuadro);
}

pair<idImagen_t, int> ObjetoMovible::getDibujable(Uint32 time, vector<ObjetoEstatico*> v_estaticos, vector<Obstaculo*> v_obstaculos, vector<ObjetoMovible*> v_movibles, ObjetoEstatico* princesa, vector<ObjetoMovible*> v_personajes) {

	if (getPrioridad() == 4) this->v_personajes = v_personajes;
	v_estaticos.push_back(princesa);

	caer(time-lastTimeActualizar, 0);
	detectarColisionesObstaculos(v_obstaculos, v_estaticos);
	if (getActivo()) detectarColisionesMovibles(v_movibles);
	Actualizar(time);
	detectarColisionesObstaculos(v_obstaculos, v_estaticos);
	if (getActivo()) detectarColisionesMovibles(v_movibles);
	lastTimeActualizar = time;
	return getDibujable(time);
}

void ObjetoMovible::detectarColisionesObstaculos (vector<Obstaculo*> v_obstaculos, vector<ObjetoEstatico*> v_estaticos) {
	unsigned int j;

	int ZONA_COLISION_X = getAncho() * 0.1;
	int ZONA_COLISION_Y = getAlto() * 0.1;

	int perIzq = getX() + ZONA_COLISION_X;
	int perDer = getX() + getAncho() - ZONA_COLISION_X;
	int perSup = getY() + ZONA_COLISION_Y;
	int perInf = getY() + getAlto();

	int perIzqAnt = posAnterior.getX() + ZONA_COLISION_X;
	int perDerAnt = posAnterior.getX() + getAncho() - ZONA_COLISION_X;
	int perSupAnt = posAnterior.getY() + ZONA_COLISION_Y;
	int perInfAnt = posAnterior.getY() + getAlto();

	for (j=0; j < v_obstaculos.size(); j++) {

		int obsIzq = v_obstaculos.at(j)->getX();
		int obsDer = v_obstaculos.at(j)->getX() + v_obstaculos.at(j)->getAncho();
		int obsSup = v_obstaculos.at(j)->getY();
		int obsInf = v_obstaculos.at(j)->getY() + v_obstaculos.at(j)->getAlto();

		int colX = hayColisionX(perIzq, perDer, obsIzq, obsDer);
		int colY = hayColisionY(perSup, perInf, obsSup, obsInf);
		int colXvieja;
		int colYvieja;

		if (colX != 0 && colY != 0) {
			colYvieja = hayColisionY(perSupAnt, perInfAnt, obsSup, obsInf);
			colXvieja = hayColisionX(perIzqAnt, perDerAnt, obsIzq, obsDer);

			if (this->getTipo() == PERSONAJE) v_obstaculos.at(j)->ColisionarConPersonaje(this, colX, colY, colXvieja, colYvieja);
			else v_obstaculos.at(j)->ColisionarConEnemigo(this, colX, colY, colXvieja, colYvieja);
		}
	}

	for (j=0; j < v_estaticos.size(); j++) {

		int obsIzq = v_estaticos.at(j)->getX();
		int obsDer = v_estaticos.at(j)->getX() + v_estaticos.at(j)->getAncho();
		int obsSup = v_estaticos.at(j)->getY();
		int obsInf = v_estaticos.at(j)->getY() + v_estaticos.at(j)->getAlto();

		int colX = hayColisionX(perIzq, perDer, obsIzq, obsDer);
		int colY = hayColisionY(perSup, perInf, obsSup, obsInf);
		int colXvieja;
		int colYvieja;

		if (colX != 0 && colY != 0) {

			colYvieja = hayColisionY(perSupAnt, perInfAnt, obsSup, obsInf);
			colXvieja = hayColisionX(perIzqAnt, perDerAnt, obsIzq, obsDer);

			if (this->getTipo() == PERSONAJE) v_estaticos.at(j)->ColisionarConPersonaje(this, colX, colY, colXvieja, colYvieja);
			else v_estaticos.at(j)->ColisionarConEnemigo(this, colX, colY, colXvieja, colYvieja);
		}
	}
}

void ObjetoMovible::detectarColisionesMovibles (vector<ObjetoMovible*> v_movibles) {

	unsigned int j=0;

	int perIzq = getX();
	int perDer = getX() + getAncho();
	int perSup = getY();
	int perInf = getY() + getAlto();

	int perIzqAnt = posAnterior.getX();
	int perDerAnt = posAnterior.getX() + getAncho();
	int perSupAnt = posAnterior.getY();
	int perInfAnt = posAnterior.getY() + getAlto();

	for (; j < v_movibles.size(); j++) {

		if (v_movibles.at(j) != this) {

			int obsIzq = v_movibles.at(j)->getX();
			int obsDer = v_movibles.at(j)->getX() + v_movibles.at(j)->getAncho();
			int obsSup = v_movibles.at(j)->getY();
			int obsInf = v_movibles.at(j)->getY() + v_movibles.at(j)->getAlto();

			int colX = hayColisionX(perIzq, perDer, obsIzq, obsDer);
			int colY = hayColisionY(perSup, perInf, obsSup, obsInf);

			if (colX != 0 && colY != 0)	{
				int colXvieja = hayColisionX(perIzqAnt, perDerAnt, obsIzq, obsDer);
				int colYvieja = hayColisionY(perSupAnt, perInfAnt, obsIzq, obsDer);

				if (this->getTipo() == PERSONAJE) v_movibles.at(j)->ColisionarConPersonaje(this, colX, colY);
				else v_movibles.at(j)->ColisionarConEnemigo(this, colX, colY, colXvieja, colYvieja);
			}
		}
	}
}

void ObjetoMovible::Morir() {
	setEstadoVida(Muerto);
}

int ObjetoMovible::hayColisionX(int perIzq, int perDer, int obsIzq, int obsDer) {

	if ((perIzq > obsIzq && perIzq < obsDer) && (perDer > obsIzq && perDer < obsDer)) return getAncho();

	if (perIzq > obsIzq && perIzq < obsDer) return obsDer-perIzq;

	if (perDer > obsIzq && perDer < obsDer) return perDer-obsIzq;

	if (perIzq <= obsIzq && perDer >= obsDer) return obsDer-obsIzq;

	return 0;
}

int ObjetoMovible::hayColisionY(int perSup, int perInf, int obsSup, int obsInf) {

	if ((perSup > obsSup && perSup < obsInf) && (perInf > obsSup && perInf < obsInf)) return getAlto();

	if (perSup > obsSup && perSup < obsInf) return obsInf-perSup;

	if (perInf > obsSup && perInf < obsInf) return perInf-obsSup;

	if (perSup <= obsSup && perInf >= obsInf) return obsInf-obsSup;

	return 0;
}
